﻿using System;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Runtime.Remoting;
using System.ServiceModel.Activation;
using Microsoft.Synchronization.Data;
using Microsoft.Synchronization.Data.Server;
using Newtonsoft.Json;
using Syncronization.Common;
using Syncronization.REST;

namespace Syncronization.Service
{
    [AspNetCompatibilityRequirements(
        RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class RestSyncService : DbServerSyncProvider, IRestSyncService
    {
        #region IRestSyncService Members

        /// <summary>
        /// Returns any changes since the last requested sync
        /// </summary>
        /// <param name="syncRequest">The sync request.</param>
        /// <returns></returns>
        public virtual mfLYSyncResponse GetChanges(mfLYSyncRequest syncRequest)
        {
            //TODO:add db ok an application key to request so you can create a generic service

            //todo take out hardcoded values by adding ApplicationName to syncRequest
            Initialize("test");
            
            SyncContext syncContext = GetChanges(mfLYSyncRequest.GetSyncGroupMetadata(syncRequest),
                                                 mfLYSyncRequest.GetSyncSession(syncRequest));
            
            string dataSet = GetChangesFromSyncContext(syncContext);

            var response = new mfLYSyncResponse
                               {
                                   Changes = dataSet,
                                   OriginatorId = syncContext.OriginatorId,
                                   TotalUpdates = syncContext.GroupProgress.TotalUpdates,
                                   TotalInserts = syncContext.GroupProgress.TotalInserts,
                                   TotalDeletes = syncContext.GroupProgress.TotalDeletes,
                                   TotalChangesPending = syncContext.GroupProgress.TotalChangesPending,
                                   TotalChangesFailed = syncContext.GroupProgress.TotalChangesFailed,
                                   TotalChangesApplied = syncContext.GroupProgress.TotalChangesApplied,
                                   TotalChanges = syncContext.GroupProgress.TotalChanges,
                                   GroupName = syncContext.GroupProgress.GroupName,
                                   BatchCount = syncContext.BatchCount
                               };

            if (syncContext.NewAnchor != null)
                response.NewAnchor = syncContext.NewAnchor.Anchor;
            if (syncContext.MaxAnchor != null)
                response.MaxAnchor = syncContext.MaxAnchor.Anchor;

            return response;
        }

        public virtual mfLYSyncResponse ApplyChanges(mfLYSyncRequest syncRequest)
        {
            //TODO:add db ok an application key to request so you can create a generic service

            //todo take out hardcoded values by adding ApplicationName to syncRequest
            Initialize("test");

            DataSet changes = null;

            var serializer = new JsonSerializer();
            //deserialize dataset and add to syncContext
            serializer.Converters.Add(new DataSetConverter());

            using (var sr = new StringReader(syncRequest.DataSet))
            {
                using (var reader = new JsonTextReader(sr))
                {
                    changes = (DataSet)serializer.Deserialize(reader, typeof(DataSet));
                }
            }
            
            SyncContext syncContext = ApplyChanges(mfLYSyncRequest.GetSyncGroupMetadata(syncRequest),
                                changes, mfLYSyncRequest.GetSyncSession(syncRequest));

            string dataSet = GetChangesFromSyncContext(syncContext);

            var response = new mfLYSyncResponse
            {
                Changes = dataSet,
                OriginatorId = syncContext.OriginatorId,
                TotalUpdates = syncContext.GroupProgress.TotalUpdates,
                TotalInserts = syncContext.GroupProgress.TotalInserts,
                TotalDeletes = syncContext.GroupProgress.TotalDeletes,
                TotalChangesPending = syncContext.GroupProgress.TotalChangesPending,
                TotalChangesFailed = syncContext.GroupProgress.TotalChangesFailed,
                TotalChangesApplied = syncContext.GroupProgress.TotalChangesApplied,
                TotalChanges = syncContext.GroupProgress.TotalChanges,
                GroupName = syncContext.GroupProgress.GroupName,
                BatchCount = syncContext.BatchCount
            };

            if (syncContext.NewAnchor != null)
                response.NewAnchor = syncContext.NewAnchor.Anchor;
            if (syncContext.MaxAnchor != null)
                response.MaxAnchor = syncContext.MaxAnchor.Anchor;

            return response;
        }

        #endregion

        #region private

        private string GetChangesFromSyncContext(SyncContext syncContext)
        {
            //in order to get the dataset to json we need to serialize and add to the response as a string 
            //value.  The better work around would be to create a custom serializer and add it to the web.config json converter section
            //that way the dataset can be strongly typed on the response and just return the response
            //the custom serialize must serialize the dataset so that a custom converter using json.net can deserialize it

            var serializer = new JsonSerializer();
            serializer.NullValueHandling = NullValueHandling.Ignore;

            serializer.Converters.Add(new DataSetConverter());

            string dataSet;
            using (TextWriter textWriter = new StringWriter())
            {
                using (JsonWriter writer = new JsonTextWriter(textWriter))
                {
                    serializer.Serialize(writer, syncContext.GroupProgress.Changes);
                }
                dataSet = textWriter.ToString();
            }
            return dataSet;
        }

        private void Initialize(string application)
        {
            //TODO:use application to get the connection string and verify apikey
            InitializeConnection("Data Source=.;Initial Catalog=RestSyncServiceFixture;Integrated Security=True;");

            InitializeSyncAdapters(application);
            InitializeNewAnchorCommand();
        }

        private void InitializeConnection(string connectionString)
        {
            Connection = new SqlConnection(connectionString);
        }

        #endregion

        #region protected

        /// <summary>
        /// Initializes the new anchor command.
        /// </summary>
        protected virtual void InitializeNewAnchorCommand()
        {
            SelectNewAnchorCommand = new SqlCommand();
            SelectNewAnchorCommand.CommandText = "Select @sync_new_received_anchor = GETUTCDATE()";
            SelectNewAnchorCommand.CommandType = CommandType.Text;
            var selectnewanchorcommand_sync_new_received_anchorParameter = new SqlParameter(
                "@sync_new_received_anchor", SqlDbType.DateTime);
            selectnewanchorcommand_sync_new_received_anchorParameter.Direction = ParameterDirection.Output;
            SelectNewAnchorCommand.Parameters.Add(selectnewanchorcommand_sync_new_received_anchorParameter);
        }

        /// <summary>
        /// Initializes the sync adapters.
        /// </summary>
        /// <param name="application">The application.</param>
        protected virtual void InitializeSyncAdapters(string application)
        {
            //TODO:get Controller info from application
            ObjectHandle objectHandle = Activator.CreateInstance("Syncronization.Service.UnitTests",
                                                                 "Syncronization.Service.UnitTests.Model.SimpleSyncSchemaController");
            if (objectHandle == null)
                return;

            var syncController = (SyncController) objectHandle.Unwrap();
            syncController.Adapters.ForEach(item => SyncAdapters.Add(item));
        }

        #endregion
    }
}